package simplifiedPastry;

import java.io.Serializable;
import java.math.BigInteger;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;

import simplifiedPastry.Node.NodeEntry;

public class LeafSet implements Serializable {
	private static final long serialVersionUID = 7156797676110354401L;

	private class DistanceComparator implements Comparator<NodeEntry>, Serializable {
		private static final long serialVersionUID = -4449393867841199524L;
		private NodeId mainNode;
		
		public DistanceComparator(NodeId mainNode) {
			this.mainNode = mainNode;
		}

		@Override
		public int compare(NodeEntry o1, NodeEntry o2) {
			  return o1.nodeId.compareTo(o2.nodeId);
		}
		
	};
	
	private TreeSet<NodeEntry> leafSetSetSmaller, leafSetSetBigger, leafSetSet;
	private NodeId nodeId;
	private NodeEntry nodeEntry;
	
	public LeafSet(NodeEntry nodeEntry) {
		this.nodeEntry		= nodeEntry;
		this.nodeId			= nodeEntry.nodeId;
		DistanceComparator distanceComparator = new DistanceComparator(nodeId);
		leafSetSetSmaller	= new TreeSet<NodeEntry>(distanceComparator);
		leafSetSetBigger	= new TreeSet<NodeEntry>(distanceComparator);
		leafSetSet			= new TreeSet<NodeEntry>(distanceComparator);
	}
	
	synchronized public void fillLeafSetByteData(byte[] buff, int offset) {
		if (leafSetSet.size() == 0) {
			return;
		}
		Iterator<NodeEntry> iter = leafSetSet.iterator();
		while (iter.hasNext()) {
			NodeEntry entry = iter.next();
			entry.fillByteBuff(buff, offset);
			offset += 22;
		}
	}
	
	synchronized public NodeEntry[] getKLeafSet(int num) {
		if (num > ProtocolVariables.leafSetSize) {
			return new NodeEntry[0];
		}
		int max = (num < leafSetSet.size()) ? num : leafSetSet.size();
		NodeEntry[] nodeEntryArray = new NodeEntry[max];
		Iterator<NodeEntry> iter = leafSetSet.iterator();
		int i = 0;
		while (iter.hasNext()) {
			nodeEntryArray[i] = iter.next();
			if (i++ >= max) {
				break;
			}
		}
		return nodeEntryArray;
	}
	
	synchronized public NodeEntry[] getLeafSet() {
		return getKLeafSet(ProtocolVariables.leafSetSize);
	}
	
	synchronized public int getSize() {
		return leafSetSet.size();
	}
	
	synchronized public void addNodeEntry(NodeEntry nodeEntry) {		
		if (nodeEntry.nodeId.compareTo(nodeId) == 0) {
			return ;
		}
		
//		Iterator<NodeEntry> i = leafSetSet.iterator();
//		System.out.print("### Moj leafSet przed: ");
//		while(i.hasNext()) {
//			System.out.print(i.next().nodeId + " ");
//		}
//		System.out.println();
		
//		System.out.println("### A teraz dodam do niego: " + nodeEntry.nodeId);
		if(leafSetSet.add(nodeEntry) == false) {
			return;
		}
		
//		i = leafSetSet.iterator();
//		System.out.print("### Moj leafSet po: ");
//		while(i.hasNext()) {
//			System.out.print(i.next().nodeId + " ");
//		}
//		System.out.println();
		
		
		// if (leafSetSet.size() > ProtocolVariables.leafSetSize) {
		//   System.out.println("### za duzo w lf " + nodeEntry.nodeId);
		//  leafSetSet.pollLast();
		// }
		
		if (nodeId.compareTo(nodeEntry.nodeId) < 0) {
//			if (leafSetSet.size() > ProtocolVariables.leafSetSize) {
//				Iterator<NodeEntry> i = leafSetSet.iterator();
//				System.out.print("### Moj leafSet przed: ");
//				while(i.hasNext()) {
//					System.out.print(i.next().nodeId + " ");
//				}
//				System.out.println();
//				System.out.println("###Node("+nodeId+") " + nodeId + " < " + nodeEntry.nodeId + " -> usuwam ostatni");;
//				leafSetSet.pollLast();
//				System.out.print("###Node("+nodeId+") Moj leafSet po: ");
//				i = leafSetSet.iterator();
//				while(i.hasNext()) {
//					System.out.print(i.next().nodeId + " ");
//				}
//				System.out.println();
//			}
			//System.out.println("### dodaje do prawego " + nodeEntry.nodeId);
			leafSetSetBigger.add(nodeEntry);
			if (leafSetSetBigger.size() > ProtocolVariables.leafSetSize/2) {
				leafSetSetBigger.pollLast();
			}
		} else {
//			if (leafSetSet.size() > ProtocolVariables.leafSetSize) {
//				Iterator<NodeEntry> i = leafSetSet.iterator();
//				System.out.print("###Node("+nodeId+") Moj leafSet przed: ");
//				while(i.hasNext()) {
//					System.out.print(i.next().nodeId + " ");
//				}
//				System.out.println();
//				System.out.println("###Node("+nodeId+") " + nodeId + " > " + nodeEntry.nodeId + " -> usuwam pierwszy");;
//				
//				leafSetSet.pollFirst();
//				System.out.print("###Node("+nodeId+") Moj leafSet po: ");
//				i = leafSetSet.iterator();
//				while(i.hasNext()) {
//					System.out.print(i.next().nodeId + " ");
//				}
//				System.out.println();
//				
//			}
			//System.out.println("### dodaje do lewego " + nodeEntry.nodeId);
			leafSetSetSmaller.add(nodeEntry);
			if (leafSetSetSmaller.size() > ProtocolVariables.leafSetSize/2) {
				leafSetSetSmaller.pollFirst();
			}
		}
		
		
			if (leafSetSet.size() > ProtocolVariables.leafSetSize) {
//			System.out.println("### za duzo w lf " + nodeEntry.nodeId);
			Iterator<NodeEntry> i = leafSetSet.iterator();
			if(i.hasNext() == true) {
				NodeEntry first = i.next();
				NodeEntry last = null;
				while(i.hasNext()) {
					last = i.next();
				}
				
				BigInteger middleBI = (last.nodeId.distance(first.nodeId)).divide(new BigInteger("2"));
				NodeId middle = new NodeId(NodeId.encodeId(middleBI.toString()));
				System.out.println("###Node("+nodeId+") (" + last.nodeId + " - " + first.nodeId + ")/2 = " + middleBI);
				System.out.println("###Node("+nodeId+") middle = " + middle.toString());
				
				i = leafSetSet.iterator();
				System.out.print("###Node("+nodeId+") Moj leafSet przed: ");
				while(i.hasNext()) {
					System.out.print(i.next().nodeId + " ");
				}
				System.out.println();
				
				if(middle.compareTo(nodeId) < 0) {
					System.out.println("###Node("+nodeId+") middle:" + middle.toString() + " < " + nodeId + "Zdejmuje: " + first.nodeId);
					leafSetSet.pollFirst();
				}
				else {
					System.out.println("###Node("+nodeId+") middle:" + middle.toString() + " > " + nodeId + "Zdejmuje: " + last.nodeId);
					leafSetSet.pollLast();
				}
				
				System.out.print("###Node("+nodeId+") Moj leafSet po: ");
				i = leafSetSet.iterator();
				while(i.hasNext()) {
					System.out.print(i.next().nodeId + " ");
				}
				System.out.println();
			}
		}
		
	}
	
	synchronized public void removeNodeEntry(NodeEntry nodeEntry) {
		leafSetSet.remove(nodeEntry);
		leafSetSetSmaller.remove(nodeEntry);
		leafSetSetBigger.remove(nodeEntry);
	}
	
	synchronized public NodeEntry getNextHopWithin(NodeId searchId) {
		//System.out.println("###leafset.getNextHopWithin()");
		if(leafSetSetSmaller.size() == 0 || leafSetSetBigger.size() == 0) {
			return null;
		}
		
		NodeEntry left = leafSetSetSmaller.first();
		NodeEntry right = leafSetSetBigger.last();
		if(left == null || right == null) {
			return null;
		}
		
		if(left.nodeId.compareTo(searchId) <= 0 &&
				right.nodeId.compareTo(searchId) >= 0) {

			Iterator<NodeEntry> i = leafSetSet.iterator();
			if(i.hasNext() == false) {
				return null;
			}
			
			NodeEntry closestEntry = i.next();
			BigInteger closestDistance = closestEntry.nodeId.distance(searchId);
			//System.out.println("###closestDistance init = " + closestDistance + " from " + closestEntry.nodeId);
			while (i.hasNext()) {
				NodeEntry entry = i.next();
				BigInteger distance = entry.nodeId.distance(searchId);
				if (distance.compareTo(closestDistance) < 0) {
					closestDistance = distance;
					closestEntry = entry;
					//System.out.println("###closestDistance = " + closestDistance + " from " + closestEntry.nodeId);
				}
			}
			

			closestDistance = closestEntry.nodeId.distance(searchId);
			BigInteger myDistance = nodeId.distance(searchId);
			if(myDistance.compareTo(closestDistance) <= 0) {
				//System.out.println("### MyDistance " + nodeId + " to " + searchId + " = " + myDistance);
				//System.out.println("### ClosesetDi " + closestEntry.nodeId + " to " + searchId + " = " + closestDistance);
				return null;
			}
			
			
			return closestEntry;
		}
		else {
			return null;
		}
	}
	
	synchronized public NodeEntry getNextNodeEntry(NodeId searchId) {
		//System.out.println("###leafset.getNextNodeEntry()");
		Iterator<NodeEntry> i = leafSetSet.iterator();
		if(i.hasNext() == false) {
			//System.out.println("###Nie mam nic w leafSet -> return null");
			return null;
		}
		
		int groupNum = nodeId.sameGroupNum(searchId);
		
		NodeEntry closestEntry = i.next();
		
		while(i.hasNext() && closestEntry.nodeId.sameGroupNum(searchId) < groupNum) {
			closestEntry = i.next();
		}
		
		if(i.hasNext() == false) {
			//System.out.println("###Tylko jeden lub 0 jest z taka sama grupa bitow -> return");
			if(closestEntry.nodeId.sameGroupNum(searchId) < groupNum) {
				return null;
			}
			
			BigInteger closestDistance = closestEntry.nodeId.distance(searchId);
			BigInteger myDistance = nodeId.distance(searchId);
			if(myDistance.compareTo(closestDistance) <= 0) {
				//System.out.println("### MyDistance " + nodeId + " to " + searchId + " = " + myDistance);
				//System.out.println("### ClosesetDi " + closestEntry.nodeId + " to " + searchId + " = " + closestDistance);
				return null;
			}
			
			return closestEntry;
		}
		
		BigInteger closestDistance = closestEntry.nodeId.distance(searchId);
		//System.out.println("###closestDistance init = " + closestDistance + " from " + closestEntry.nodeId);
		
		while (i.hasNext()) {
			NodeEntry entry = i.next();
			if(entry.nodeId.sameGroupNum(searchId) >= groupNum) {
				BigInteger distance = entry.nodeId.distance(searchId);
				if (distance.compareTo(closestDistance) < 0) {
					closestDistance = distance;
					closestEntry = entry;
					//System.out.println("###closestDistance = " + closestDistance + " from " + closestEntry.nodeId);
				}
			}
		}
		
		closestDistance = closestEntry.nodeId.distance(searchId);
		BigInteger myDistance = nodeId.distance(searchId);
		if(myDistance.compareTo(closestDistance) <= 0) {
			//System.out.println("### MyDistance " + nodeId + " to " + searchId + " = " + myDistance);
			//System.out.println("### ClosesetDi " + closestEntry.nodeId + " to " + searchId + " = " + closestDistance);
			return null;
		}
		
		return closestEntry;
		
		
		
		/*
		Iterator<NodeEntry> i = leafSetSet.iterator();
		NodeEntry closestEntry = nodeEntry;
		BigInteger closestDistance = nodeEntry.nodeId.distance(searchId);
		while (i.hasNext()) {
			NodeEntry entry = i.next();
			BigInteger distance = entry.nodeId.distance(searchId);
			if (distance.compareTo(closestDistance) < 0) {
				closestDistance = distance;
				closestEntry = entry;
			}
		}
		if (closestEntry == nodeEntry) {
			return null;
		}
		return closestEntry;*/
	}

	@Override
	public String toString() {
		Iterator<NodeEntry> iter = leafSetSet.iterator();
		StringBuilder s = new StringBuilder();
		s.append("[");
		while (iter.hasNext()) {
			NodeEntry e = iter.next();
			s.append(e.nodeId + " " + e.inetAddress + " " + e.port + "\n,");
		}
		s.insert(s.length()-1, ']');
		return s.toString();
	}
}
